/*
 * $Id: PendingChangelistView.java 23 2002-09-29 13:59:30Z bpruessmann $
 *
 * Copyright (c) 2001 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 * 	  David Corbin  - added Revert action
 */
package net.sourceforge.p4eclipse.ui.views;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import net.sourceforge.p4eclipse.core.IResourceStateChangeListener;
import net.sourceforge.p4eclipse.core.PerforceException;
import net.sourceforge.p4eclipse.core.PerforceRepositoryProvider;
import net.sourceforge.p4eclipse.core.Policy;
import net.sourceforge.p4eclipse.core.api.IChangesResult;
import net.sourceforge.p4eclipse.core.api.IOpenedResult;
import net.sourceforge.p4eclipse.core.api.IWhereResult;
import net.sourceforge.p4eclipse.internal.ui.dnd.OpenedResultTransfer;
import net.sourceforge.p4eclipse.internal.ui.dnd.PendingChangelistsDragAdapter;
import net.sourceforge.p4eclipse.internal.ui.dnd.PendingChangelistsDropAdapter;
import net.sourceforge.p4eclipse.internal.ui.model.ClientPendingChangelistsContainer;
import net.sourceforge.p4eclipse.internal.ui.model.OtherPendingChangelistsContainer;
import net.sourceforge.p4eclipse.ui.PerforceUIPlugin;
import net.sourceforge.p4eclipse.ui.dialogs.ChangeSpecDialog;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;

/**
  * The PendingChangelistView defines a view for managing Perforce changelists.
  * Compare with P4Win.
  * 
  * @version 	$Revision: 23 $
  * @author		<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author		<a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class PendingChangelistView extends ViewPart
{
	// -------------------------------------------------------- Public Constants
	
	/** The view's identifier. */
	public static final String ID = 
		PerforceUIPlugin.ID + ".ui.views.PendingChangelistView";   //$NON-NLS-1$	
	
	// ------------------------------------------------ Private Static Variables
	
	/** Stores the singleton instance. */
	private static PendingChangelistView instance;
	
	// ------------------------------------------------------- Private Variables

	private Root root;
	private TreeViewer viewer;	
	private DrillDownAdapter drillPart;
	private RefreshAction refreshAction;
	private NewChangelistAction newChangelistAction;
	private RevertAction revertAction;

	// ----------------------------------------------------- Public Constructors

	/** Constructor for PendingChangelistView. */
	public PendingChangelistView()
	{
		instance = this;

		PerforceUIPlugin.getPlugin().addResourceStateChangeListener(
			new ResourceStateChangeListener());
	}

	// --------------------------------------------------- Public Static Methods
		
	/** @return the singleton instance. */
	public static final PendingChangelistView getInstance()
	{
		return instance;
	}

	// ---------------------------------------------------------- Public Methods
	
	/** Refreshes the current client's pending changelists. */
	public void refreshClientChangelists()
	{
		viewer.refresh(root.children[0]);
	}

	/** @see IWorkbenchPart#createPartControl(Composite) */
	public void createPartControl(Composite parent)
	{
		root = new Root();
		
		viewer = 
			new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(new ContentProvider());	
		viewer.setLabelProvider(new WorkbenchLabelProvider());
		viewer.setSorter(new InternalSorter());
		viewer.setInput(root);		
		viewer.getControl().addKeyListener(new KeyAdapter() 
		{
			public void keyPressed(KeyEvent event) 
			{
				if (event.keyCode == SWT.F5) 
					refreshAction.run();
			}
		});
			
		drillPart = new DrillDownAdapter(viewer);
									
		getSite().setSelectionProvider(viewer);			
		contributeActions();
		initDragAndDrop();
	}

	/** @see IWorkbenchPart#setFocus() */
	public void setFocus()
	{
		Policy.assertNotNull(viewer);
		viewer.getControl().setFocus();
	}
	
	// --------------------------------------------------------- Private Methods
	
	/**
	  * Adds drag and drop support to the pending changelists view.
	  */
	private void initDragAndDrop() 
	{
		int operations = DND.DROP_MOVE;
		Transfer[] transfers = new Transfer[]
		{ 
			OpenedResultTransfer.getInstance() 
		};
		
		PendingChangelistsDragAdapter dragAdapter = 
			new PendingChangelistsDragAdapter(viewer);		
		viewer.addDragSupport(operations, transfers, dragAdapter);
		
		PendingChangelistsDropAdapter dropAdapter = 
			new PendingChangelistsDropAdapter(viewer);
		dropAdapter.setFeedbackEnabled(false);
		viewer.addDropSupport(operations, transfers, dropAdapter);
	}	

	/** 
	  * Contribute actions to the view. 
	  */
	private void contributeActions() 
	{
		viewer.addDoubleClickListener(new DoubleClickListener());
		
		refreshAction = new RefreshAction();		
		revertAction = new RevertAction();		
		newChangelistAction = new NewChangelistAction();

		// Create the popup menu
		MenuManager menuMgr = new MenuManager();
		Tree tree = viewer.getTree();
		Menu menu = menuMgr.createContextMenu(tree);
		menuMgr.addMenuListener(new IMenuListener() 
		{
			public void menuAboutToShow(IMenuManager manager) 
			{
				// Misc additions go last
				manager.add(
					new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
									
				manager.add(new Separator(IWorkbenchActionConstants.SEP));					
				manager.add(newChangelistAction);						
				manager.add(refreshAction);	
				
				String clientName = PerforceUIPlugin.getClientName();
				IStructuredSelection sel = 
					(IStructuredSelection)viewer.getSelection();
				for (Iterator i = sel.iterator(); i.hasNext(); )
				{
					IAdaptable element = (IAdaptable)i.next();
					if (element instanceof IOpenedResult)
					{
						IOpenedResult openResult = (IOpenedResult)element;
						
						if (openResult.getClientName().equals(clientName))
						{
							manager.add(
								new Separator(IWorkbenchActionConstants.SEP));
							manager.add(revertAction);			
							break;
						}
					}
				}

			}
		});
		menuMgr.setRemoveAllWhenShown(true);
		tree.setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	
		// Create the local tool bar
		IActionBars bars = getViewSite().getActionBars();
		IToolBarManager tbm = bars.getToolBarManager();
		drillPart.addNavigationActions(tbm);
		tbm.add(refreshAction);
		tbm.update(false);	
	}

	// --------------------------------------------------- Private Inner Classes
	
	private class ResourceStateChangeListener 
		implements IResourceStateChangeListener	
	{
		public void resourceStateChanged(IResource[] changedResources)
		{
			Display display = Display.getCurrent();
			if (display == null)
			{
				display = Display.getDefault();
			}
			
			display.asyncExec(new Runnable() 
			{
			     public void run() 
			     {
					refreshClientChangelists();
			     }		
			});
		}
	}
	
	private class DoubleClickListener implements IDoubleClickListener
	{
		/**
		  * @see IDoubleClickListener#doubleClick(DoubleClickEvent)
		  */
		public void doubleClick(DoubleClickEvent event) 
		{
			IStructuredSelection selection = 
				(IStructuredSelection)viewer.getSelection();
			IAdaptable element = (IAdaptable)selection.getFirstElement();
			
			if (element instanceof IChangesResult)
			{
				doubleClickChangesResult((IChangesResult)element);
			}
			else
			if (element instanceof IOpenedResult)
			{
				doubleClickOpenedResult((IOpenedResult)element);
			}
			else
			{
				drillPart.goInto();
			}
		}

		/** Performs action that handles double clicks on IChangesResult
		  * elements.
		  */
		private void doubleClickChangesResult(IChangesResult changesResult) 
		{
			String clientname = 
				PerforceUIPlugin.getClient().info().getClientName();
			
			if (changesResult.getChangelist().intValue() != -1)
			{
				try
				{
					Integer changelist = changesResult.getChangelist();					
					ChangeSpecDialog dialog = 
						ChangeSpecDialog.newChangeSpecDialog(null, changelist);
					
					if (!(clientname.equals(changesResult.getClientName())))
					{
						dialog.setReadOnly(true);
					}
						
					if (dialog.open() == ChangeSpecDialog.OK && 
					    !dialog.isReadOnly())
					{
						String changeSpec = dialog.getChangeSpecString();
						PerforceUIPlugin.getClient().change(changeSpec);
						
						refreshClientChangelists();
					}
				}
				catch (PerforceException ex)
				{
					ErrorDialog.openError(
						null, 
						"Perforce Error", 
						"Failure.", 
						ex.getStatus());
				}
			}
		}
		
		/** Performs action that handles double clicks on IOpenedResult
		  * elements.
		  */
		private void doubleClickOpenedResult(IOpenedResult openedResult)
		{

			IWorkbench workbench = 
				PerforceUIPlugin.getPlugin().getWorkbench();					
			IEditorRegistry registry = workbench.getEditorRegistry();
			IWorkbenchPage page = 
				workbench.getActiveWorkbenchWindow().getActivePage();				
			

			String id;
			IFile file;
			try
			{
				IWhereResult whereResult = 
					PerforceUIPlugin.getClient().where(
						openedResult.getClientFilename());
						
				IEditorDescriptor descriptor = 
					registry.getDefaultEditor(whereResult.getLocalFilename());					
				if (descriptor == null) 
				{
					id = "org.eclipse.ui.DefaultTextEditor";       //$NON-NLS-1$
				} 
				else 
				{
					id = descriptor.getId();
				}		
				
				IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
				file = root.getFileForLocation(
					new Path(whereResult.getLocalFilename()));
			}
			catch (PerforceException e)
			{
				ErrorDialog.openError(
					null, 
					"Perforce Error", 
					"A Perforce operation (p4 where) failed.", 
					e.getStatus());
					
				return;
			}
			
			try
			{
				page.openEditor(file, id);
			}
			catch (PartInitException e)
			{		
			}						
		}		
	};

	private class RefreshAction extends Action
	{
		public RefreshAction()
		{
			super("Refresh View");

			setImageDescriptor(
				PerforceUIPlugin.getPlugin().getImageDescriptor(
					PerforceUIPlugin.IMG_REFRESH));
		}
		
		/** @see Action#run() */
		public void run() 
		{
			viewer.refresh();
		}	
	};
	
	private class RevertAction extends Action
	{
		RevertAction()
		{
			super("Revert");
		}
		
		/** @see Action#run() */
		public void run()
		{
			ArrayList fileNameList = new ArrayList();
			try
			{	
				String clientName = PerforceUIPlugin.getClientName();
				IStructuredSelection sel = 
					(IStructuredSelection)viewer.getSelection();

				for (Iterator i = sel.iterator(); i.hasNext(); )
				{
					IAdaptable element = (IAdaptable)i.next();
					if (element instanceof IOpenedResult)
					{
						IOpenedResult change = (IOpenedResult)element;
						
						if (change.getClientName().equals(clientName))
							fileNameList.add(change.getClientFilename());	
					}
				}
				
				if (fileNameList.size() > 0)
				{
					String[] fileNames = new String[fileNameList.size()];
					fileNameList.toArray(fileNames);

					PerforceUIPlugin.getClient().revert(fileNames);
				}
			}
			catch(PerforceException e)
			{
				ErrorDialog.openError(
					null, 
					"Perforce Error", 
					"A Perforce operation (p4 revert) failed.", 
					e.getStatus());
			}

//          viewer.refresh(); is not needed as refreshResourceTree will create 
//          events for resource state changes which will toggle the refresh.			
			
			try
			{
				refreshResourceTree(
					convertRemoteFilesToLocalFiles(fileNameList));
			}
			catch(Exception ce)
			{
				// eat the exception.  refreshes aren't *critical*
				// we refresh the viewer here because resource state change
				// events were most likely not generated.
				refreshClientChangelists();
			}
		}	
		
		private void refreshResourceTree(List fileNames)
			throws CoreException, TeamException
		{
			NullProgressMonitor monitor = new NullProgressMonitor();
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			IResource[] resources = new IResource[fileNames.size()];
			int index = 0;
			for (Iterator iter = fileNames.iterator();iter.hasNext(); index++)
			{
				String fileName = (String) iter.next();
				IPath path = new Path(fileName);
				IFile file = root.getFileForLocation(path);
				file.refreshLocal(IResource.DEPTH_ONE, monitor);
				resources[index] = file;
			}
		
			PerforceRepositoryProvider provider = 
				(PerforceRepositoryProvider)RepositoryProvider.getProvider(
					resources[0].getProject());
			provider.refreshState(resources, IResource.DEPTH_INFINITE, monitor);
		}
		
		private List convertRemoteFilesToLocalFiles(List remoteFiles)
			throws PerforceException
		{
			List localFiles = new ArrayList();
			for (Iterator i = remoteFiles.iterator(); i.hasNext(); )
			{
				String remoteFile = (String) i.next();
				
				IWhereResult whereResult = 
					PerforceUIPlugin.getClient().where(remoteFile);
				localFiles.add(whereResult.getLocalFilename());
			}
			
			return localFiles;
		}			
	};

	private class NewChangelistAction extends Action
	{
		public NewChangelistAction()
		{
			super("New Changelist...");
		}

		/** @see Action#run() */	
		public void run()
		{
			try
			{
				ChangeSpecDialog dialog = 
					ChangeSpecDialog.newChangeSpecDialog(null, null);
					
				if (dialog.open() == InputDialog.OK)
				{
					PerforceUIPlugin.getClient().change(
						dialog.getChangeSpecString());
					
					refreshClientChangelists();
				}			
			}
			catch (PerforceException e)
			{
				ErrorDialog.openError(
					null, 
					"Perforce Error", 
					"A Perforce operation (p4 change) failed.", 
					e.getStatus());
			}
		}
	}

	// -------------------------------------------------- Private Nested Classes
	
	/**
	  * This class provides all the root elements shown in the Changelist view.
	  */
	private static class Root implements IWorkbenchAdapter, IAdaptable
	{
		private Object[] children = 
		{ 
			new ClientPendingChangelistsContainer(), 
			new OtherPendingChangelistsContainer() 
		};
		
		/** @see IWorkbenchAdapter#getChildren(Object) */
		public Object[] getChildren(Object o) 
		{ 
			return children;
		}

		/** @see IWorkbenchAdapter#getImageDescriptor(Object) */
		public ImageDescriptor getImageDescriptor(Object object) 
		{ 
			return null; 
		}

		/** @see IWorkbenchAdapter#getLabel(Object) */
		public String getLabel(Object o) 
		{ 
			return null; 
		}

		/** @see IWorkbenchAdapter#getParent(Object) */
		public Object getParent(Object o) 
		{ 
			return null; 
		}

		/** @see IAdaptable#getAdapter(Class) */
		public Object getAdapter(Class adapter) 
		{ 
			return (adapter == IWorkbenchAdapter.class) ? this : null; 
		}
	}	

	/**
	  * This class makes the tree view a little "smarter" ;)
	  */
	private static class ContentProvider extends WorkbenchContentProvider
	{
		/** @see ITreeContentProvider#hasChildren(Object) */
		public boolean hasChildren(Object element)
		{
			if (element == null)
			{
				return false;
			}
				
			if (element instanceof IOpenedResult)
			{
				return false;
			}
			
			return true;			
		}
	}	

	/**
	  * Class responsible for sorting the tree.
	  */
	private static class InternalSorter extends ViewerSorter
	{
		/** @see ViewerSorter#compare(Viewer, Object, Object) */
		public int compare(Viewer viewer, Object e1, Object e2)
		{
			if (!(e1 instanceof IChangesResult))
			{
				return super.compare(viewer, e1, e2);
			}
				
			if (!(e2 instanceof IChangesResult))
			{
				return super.compare(viewer, e1, e2);
			}
				
			IChangesResult changesResult1 = (IChangesResult)e1;
			IChangesResult changesResult2 = (IChangesResult)e2;			
				
			return changesResult1.getChangelist().intValue() - 
			       changesResult2.getChangelist().intValue();
		}

	}
}
